package hikari.gui.assets;

import com.gooddies.wiring.Wiring;
import hikari.utils.Utils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

/**
 * @author sad
 */
public class AssetsInstallationDialog extends javax.swing.JDialog {

    private File assetFile;
    private int conflictedFiles = 0;
    private int filesCount = 0;
    private boolean inProgress = false;

    public AssetsInstallationDialog(java.awt.Dialog parent, boolean modal) {
        super(parent, modal);
        initComponents();
        assetsTree.setCellRenderer(Wiring.getComponent(AssetsTreeRenderer.class));
        pack();
        setLocationRelativeTo(null);
    }

    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jLabel1 = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        assetsTree = new javax.swing.JTree();
        installButton = new javax.swing.JButton();
        cancelButton = new javax.swing.JButton();
        workProgressbar = new javax.swing.JProgressBar();
        titleLabel = new javax.swing.JLabel();

        setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
        setTitle("Assets installation");
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });

        jLabel1.setBackground(java.awt.Color.white);
        jLabel1.setText("Installation of the Asset to the media folder");
        jLabel1.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 10, 1, 1));
        jLabel1.setOpaque(true);

        javax.swing.tree.DefaultMutableTreeNode treeNode1 = new javax.swing.tree.DefaultMutableTreeNode("Root");
        assetsTree.setModel(new javax.swing.tree.DefaultTreeModel(treeNode1));
        jScrollPane1.setViewportView(assetsTree);

        installButton.setText("Install");
        installButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                installButtonActionPerformed(evt);
            }
        });

        cancelButton.setText("Cancel");
        cancelButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cancelButtonActionPerformed(evt);
            }
        });

        titleLabel.setText("Files in the assets archive");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, 429, Short.MAX_VALUE)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jScrollPane1)
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addGap(0, 0, Short.MAX_VALUE)
                        .addComponent(installButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(cancelButton))
                    .addComponent(workProgressbar, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(titleLabel)
                        .addGap(0, 0, Short.MAX_VALUE)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(titleLabel)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 263, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(workProgressbar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 8, Short.MAX_VALUE)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(installButton)
                    .addComponent(cancelButton))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void installButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_installButtonActionPerformed
        install();
    }//GEN-LAST:event_installButtonActionPerformed

    private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
        closeWindow();
    }//GEN-LAST:event_cancelButtonActionPerformed

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        closeWindow();
    }//GEN-LAST:event_formWindowClosing
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JTree assetsTree;
    private javax.swing.JButton cancelButton;
    private javax.swing.JButton installButton;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JLabel titleLabel;
    private javax.swing.JProgressBar workProgressbar;
    // End of variables declaration//GEN-END:variables

    private void closeWindow() {
        if (!inProgress) {
            setVisible(false);
        }
    }

    public void setAssetFile(File file) {
        this.assetFile = file;
        String[] files = getFileNamesInZip(file);
        for (String filePath : files) {
            addBranch(filePath);
        }
        getTreeModel().nodeStructureChanged((TreeNode) getTreeModel().getRoot());
        if (conflictedFiles != 0) {
            titleLabel.setText(titleLabel.getText() + " (conflicted files marked with red color)");
        }
    }

    private void addBranch(String file) {
        String[] files = file.split("\\/");
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) getTreeModel().getRoot();
        DefaultMutableTreeNode currentNode = root;
        for (int i = 1; i < files.length; i++) {
            String part = files[i];
            AssetsNode childNode = getNodeChildByName(currentNode, part);
            if (childNode == null) {
                AssetsNode node = new AssetsNode(part);
                node.setAllowsChildren(false);
                if (!currentNode.getAllowsChildren()) {
                    currentNode.setAllowsChildren(true);
                }
                currentNode.add(node);
                filesCount++;
                currentNode = node;
                TreePath path = new TreePath(getTreeModel().getPathToRoot(node));
                if (!isAssetsTreePathDirectory(path) && isAssetsTreePathExist(path)) {
                    node.setConflictNode(true);
                    conflictedFiles++;
                } else {
                    node.setConflictNode(false);
                }
            } else {
                currentNode = childNode;
            }
        }
    }

    private boolean isAssetsTreePathDirectory(TreePath path) {
        return Wiring.getComponent(AssetsTree.class).isTreePathFolder(path);
    }

    private boolean isAssetsTreePathExist(TreePath path) {
        String filePath = Wiring.getComponent(AssetsTree.class).treePathToFilePath(path);
        return new File(filePath).exists();
    }

    private AssetsNode getNodeChildByName(DefaultMutableTreeNode root, String childName) {
        int childCount = root.getChildCount();
        for (int i = 0; i < childCount; i++) {
            AssetsNode node = (AssetsNode) root.getChildAt(i);
            if (node.toString().equals(childName)) {
                return node;
            }
        }

        return null;
    }

    private DefaultTreeModel getTreeModel() {
        return (DefaultTreeModel) assetsTree.getModel();
    }

    private String[] getFileNamesInZip(File file) {
        List<String> names = new ArrayList<>();
        try {
            try (ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(file)))) {
                ZipEntry entry;
                while (!((entry = zis.getNextEntry()) == null)) {
                    String name = entry.getName();
                    if (!"metadata.xml".equals(name)) {
                        names.add(name);
                    }
                }
            }
            return names.toArray(new String[0]);
        } catch (IOException ex) {
            Logger.getLogger(AssetsInstallationDialog.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    private void install() {
        if (conflictedFiles != 0) {
            Boolean result = Wiring.getComponent(Utils.class).confirm(this, "Overwriting", "Are you sure you want to overwrite conflicted files?");
            if (result == null && result == false) {
                return;
            }
        }

        workProgressbar.setMaximum(filesCount);
        workProgressbar.setValue(0);
        workProgressbar.setString("UnZiping");
        workProgressbar.setStringPainted(true);
        installButton.setEnabled(false);
        cancelButton.setEnabled(false);
        inProgress = true;
        SwingWorker worker = new SwingWorker() {
            @Override
            protected Object doInBackground() throws Exception {
                recursivelyUnzip(assetFile);
                return true;
            }

            @Override
            protected void done() {
                Wiring.getComponent(Utils.class).showMessage("Asset installed");
                closeWindow();
            }
        };
        worker.execute();
    }

    private void increaseProgressBar() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                workProgressbar.setValue(workProgressbar.getValue() + 1);
            }
        });
    }

    private void copyStreamToFile(InputStream zis, final String filePath) {
        try {
            try (BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(filePath))) {
                for (;;) {
                    int value = zis.read();
                    if (value == -1) {
                        break;
                    }

                    stream.write(value);
                }
            }
        } catch (final Exception ex) {
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    @Override
                    public void run() {
                        Wiring.getComponent(Utils.class).errorMessage("Cannot write file " + filePath + " from archive. Skipped. " + ex.getLocalizedMessage());
                    }
                });
            } catch (InterruptedException | InvocationTargetException ex1) {
                Logger.getLogger(AssetArchiver.class.getName()).log(Level.SEVERE, null, ex1);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void recursivelyUnzip(File file) {
        try {
            try (ZipFile zipFile = new ZipFile(file)) {
                String startDir = "data/";
                Enumeration<ZipEntry> zipEntries = (Enumeration<ZipEntry>) zipFile.entries();
                while (zipEntries.hasMoreElements()) {
                    ZipEntry entry = zipEntries.nextElement();
                    String name = entry.getName();
                    if (name.startsWith(startDir)) {
                        String path = name.substring(startDir.length());
                        if (path.isEmpty()) {
                            continue;
                        }
                        String destFilePath = Wiring.getComponent(AssetsTree.class).getAbsoluteAssetPathFromRelativePath(path);
                        if (!entry.isDirectory()) {
                            new File(destFilePath).mkdirs();
                            InputStream stream = zipFile.getInputStream(entry);
                            copyStreamToFile(stream, destFilePath);
                            increaseProgressBar();
                        }
                    }
                }
            }
        } catch (final IOException ex) {
            Logger.getLogger(AssetsInstallationDialog.class.getName()).log(Level.SEVERE, null, ex);
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    Wiring.getComponent(Utils.class).errorMessage("Error while unzipping asset." + ex.getLocalizedMessage());
                }
            });
        } finally {
            inProgress = false;
        }
    }
}